import { createAsyncThunk, createSlice, isRejected } from "@reduxjs/toolkit";
import { request } from "../util/Net";
import { RootState } from "./store";
import { encryptSha256 } from "../util/Crypto";
import { globalActions } from "./Global";

// 1. Requests and Responses, for communication with backend.

// 1.1 Requests.

/**
 * Request to fetch personal information of the current user.
 * @path /service/user/detail
 */
type FetchPersonalInformationRequest = null;

/**
 * Request to update password of the current user.
 * @path /service/user/updatePassword
 */
interface UpdatePasswordRequest {
  password: string;
};

/**
 * Request tp upgrade membership by invite code.
 * @path /service/user/upgradeMembership
 */
interface UpgradeMembershipRequest {
  inviteCode: string;
};

// 1.2 Responses.

/**
 * Response from fetch personal information module.
 * @path /service/user/detail
 */
interface FetchPersonalInformationResponse {
  id: number;
  userName: string;
  authority: string;
};

/**
 * Response from upgrade membership module.
 * @path /service/user/upgradeMembership
 */
interface UpgradeMembershipResponse {
  authority: string;
};

/**
 * Response from update password module.
 * @path /service/user/updatePassword
 */
type UpdatePasswordResponse = null;

// 2. Params for reduers.

interface UpdatePasswordParams {
  password: string;
};

interface UpdateMembershipParams {
  inviteCode: string;
};

// 3. State and initial value definition.

interface PersonalInformationState {
  loaded: boolean;
  id: number;
  userName: string;
  authority: string;
};

const initialState: PersonalInformationState = {
  loaded: false,
  id: 0,
  userName: '',
  authority: ''
};

// 4. Async functions definition.

/**
 * Async reducer.
 * Fetch information of the current user.
 * @example const dispatch = useAppDispatch();
 * dispatch(fetchPersonalInformation());
 */
const fetchPersonalInformation = createAsyncThunk('api/fetchPersonalInformation', async (_: void, thunkAPI) => {
  const response = await request<FetchPersonalInformationResponse, FetchPersonalInformationRequest>('/service/user/detail');
  if (response.data.code !== 0) {
    thunkAPI.dispatch(globalActions.postAlert({ title: 'Error', message: response.data.message, type: 'warning' }));
    return thunkAPI.rejectWithValue(response.data);
  }
  return response.data;
});

/**
 * Async reducer.
 * Update password for the current user.
 * @param params params of this reducer, including the plain-text password
 * @example const dispatch = useAppDispatch();
 * dispatch(updatePassword({ password: 'plain_password' }));
 */
const updatePassword = createAsyncThunk('api/updatePassword', async (params: UpdatePasswordParams, thunkAPI) => {
  const passwordHash: string = encryptSha256(params.password);
  const response = await request<UpdatePasswordResponse, UpdatePasswordRequest>('/service/user/updatePassword', {
    password: passwordHash
  });
  if (response.data.code === 0) {
    thunkAPI.dispatch(globalActions.postAlert({ title: 'Success', message: 'Password successfully updated.', type: 'success' }));
  }
  else {
    thunkAPI.dispatch(globalActions.postAlert({ title: 'Error', message: response.data.message, type: 'warning' }));
    return thunkAPI.rejectWithValue(response.data);
  }
  return response.data;
});

/**
 * Async reducer.
 * Change user group of this user via re-inputing invite code.
 * @param params params of this reducer, incuding the new invite code
 * @example const dispatch = useAppDispatch();
 * dispatch(upgradeMembership({ inviteCode: '123456' }));
 */
const upgradeMembership = createAsyncThunk('api/upgradeMembership', async (params: UpdateMembershipParams, thunkAPI) => {
  const response = await request<UpgradeMembershipResponse, UpgradeMembershipRequest>('/service/user/upgradeMembership', {
    inviteCode: params.inviteCode
  });
  if (response.data.code === 0) {
    thunkAPI.dispatch(globalActions.postAlert({ title: 'Success', message: 'Your authority is successfully updated.', type: 'success' }));
  }
  else {
    thunkAPI.dispatch(globalActions.postAlert({ title: 'Error', message: response.data.message, type: 'warning' }));
    return thunkAPI.rejectWithValue(response.data);
  }
  return response.data;
});

// 5. Slice definition.

const personalInformationSlice = createSlice({
  name: 'personalInformation',
  initialState: initialState,
  reducers: {
    clear: (state) => {
      state.id = 0;
      state.authority = '';
      state.userName = '';
      state.loaded = false;
    }
  },
  extraReducers: (builder) => {
    builder.addCase(fetchPersonalInformation.fulfilled, (state, action) => {
      state.loaded = true;
      state.userName = action.payload.result.userName;
      state.id = action.payload.result.id;
      state.authority = action.payload.result.authority;
    }).addCase(updatePassword.fulfilled, (state, action) => {

    }).addCase(upgradeMembership.fulfilled, (state, action) => {
      state.authority = action.payload.result.authority;
    }).addMatcher(isRejected, (state, action) => {
      console.error(action.payload);
    });
  }
});

// 6. Selectors for state in this module.

export const selectPersonalLoaded = (state: RootState) => state.PersonalInformation.loaded;
export const selectPersonalId = (state: RootState) => state.PersonalInformation.id;
export const selectPersonalName = (state: RootState) => state.PersonalInformation.userName;
export const selectPersonalAuthority = (state: RootState) => state.PersonalInformation.authority;

// 7. Export necessary modules.

export default personalInformationSlice.reducer;
export const personalInformationActions = personalInformationSlice.actions;
export { fetchPersonalInformation, updatePassword, upgradeMembership };
